<template>
  <div class="flex flex-col h-screen">
    <div
      class="flex flex-nowrap fixed w-full items-baseline top-0 px-6 py-4 bg-gray-100"
    >
      <div class="text-2xl font-bold">讯飞星火大模型</div>
      <div class="ml-4 text-sm text-gray-500">
        讯飞星火认知大模型-AI大语言模型-星火大模型-科大讯飞
      </div>
    </div>

    <div class="flex-1 mx-2 mt-20 mb-2" ref="chatListDom">
      <div
        v-for="(item, key) of messageList.filter((v) => v.role !== 'system')"
        :key="key"
        class="group flex flex-col px-4 py-3 rounded-lg shadow-md mb-6"
        :class="key % 2 === 0 ? 'bg-blue-400' : 'bg-indigo-400'"
      >
        <div class="flex justify-between items-center mb-2">
          <div class="font-bold">{{ roleAlias[item.role] }}：</div>
          <Copy class="group-hover:visible" :content="item.content" />
        </div>
        <div>
          <div
            class="prose text-sm text-white leading-relaxed"
            v-if="item.content"
            v-html="md.render(item.content)"
          ></div>
          <Loading v-else />
        </div>
      </div>
    </div>

    <div class="sticky bottom-0 w-full p-6 pb-8 bg-gray-100">
      <div class="flex">
        <el-input
          size="large"
          style="width: 95%;margin-right: 30px;"
          v-model="messageContent"
          placeholder="请输入你要问的问题"
          @keydown.enter="sendChatMessage()"
        />
        <el-button type="primary" size="large" @click="sendChatMessage()" :loading="isTalking">发送</el-button>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import type { ChatMessage } from "@/types";
import { ref, watch, nextTick, onMounted } from "vue";
import Loading from "@/components/Loading.vue";
// @ts-ignore
import Copy from "@/components/Copy.vue";
import { md } from "@/libs/markdown";
import CryptoJS from "crypto-js";
// @ts-ignore
import * as base64 from "base-64"
import { ElMessage, ElMessageBox } from 'element-plus'

let isTalking = ref(false);
let messageContent = ref("");
const socket = ref<any>(null)
const apiData = ref({
  APPID: '047aee26',
  APISecret: 'MTE5MjU4NWMzNTIwNzY0ZGU3ZjEzYTFl',
  APIKey: 'c7c47406959b7c2bded50a6433e30c1b',
})
const chatListDom = ref<HTMLDivElement>();
const roleAlias = { user: "ME", assistant: "讯飞星火大模型", system: "System" };
const messageList = ref<ChatMessage[]>([
  {
    role: "system",
    content: "我是讯飞星火大模型，尽可能简洁地回答。",
  },
  {
    role: "assistant",
    content: `你好，我是AI语言模型，我可以提供一些常用服务和信息，例如：

1. 代码工程师：我将根据您的要求写出示例代码，您可以尝试这样问我：“开发一款消消乐小游戏界面，用python语言”。

2. 文本扩写：请输入文本，我来帮你扩写，比如“大语言模型的发展将为生活带来便利”。

3. 编程知识问答：请输入您在编程中的问题，比如“在运行时发现有bug，该如何去调试和解决问题。

请告诉我你需要哪方面的帮助，我会根据你的需求给你提供相应的信息和建议。`,
  },
]);

onMounted(() => {
  verifyTokenInfo()
})

const checkTokenInfo = () => {
  return new Promise((resolve, reject) => {
    ElMessageBox.prompt('深圳子公司叫什么名字?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消'
    })
      .then(({ value }) => {
        if (value === '深圳鹏城信息科技有限公司') {
          localStorage.setItem('tokenInfo', '深圳鹏城信息科技有限公司');
          // @ts-ignore
          resolve();
        } else {
          reject(new Error('校验失败'));
        }
      })
      .catch(() => {
        reject(new Error('校验失败'));
      });
  });
}

const verifyTokenInfo = async () => {
  let tokenInfo = localStorage.getItem('tokenInfo');

  while (tokenInfo !== '深圳鹏城信息科技有限公司') {
    try {
      await checkTokenInfo();
      tokenInfo = '深圳鹏城信息科技有限公司';
      ElMessage({
        type: 'success',
        message: `校验成功`,
      });
    } catch (error) {
      tokenInfo = '';
      ElMessage({
        type: 'error',
        message: `校验失败`,
      });
    }
  }
}

const sendChatMessage = async (content: string = messageContent.value) => {
  let tokenInfo = localStorage.getItem('tokenInfo');

  if (tokenInfo === '深圳鹏城信息科技有限公司') {
    try {
      if (!messageContent.value.length) return;

      isTalking.value = true;
      if (messageList.value.length === 2) {
        messageList.value.pop();
      }

      messageList.value.push({ role: "user", content });
      messageList.value.push({ role: "assistant", content: "" });

      callSparkModel()
    } catch (error: any) {
      appendLastMessageContent(error);
    }
  } else {
    verifyTokenInfo()
  }
};

const getWebSocketUrl = () => {
  return new Promise((resolve, reject) => {
    let url = "wss://spark-api.xf-yun.com/v1.1/chat";
    let host = "spark-api.xf-yun.com";
    let apiKeyName = "api_key";
    // @ts-ignore
    let date = new Date().toGMTString();
    let algorithm = "hmac-sha256";
    let headers = "host date request-line";
    let signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v1.1/chat HTTP/1.1`;
    let signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiData.value.APISecret);
    let signature = CryptoJS.enc.Base64.stringify(signatureSha);
    let authorizationOrigin = `${apiKeyName}="${apiData.value.APIKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
    let authorization = base64.encode(authorizationOrigin);
    url = `${url}?authorization=${authorization}&date=${encodeURI(date)}&host=${host}`;

    resolve(url);
  });
};

const callSparkModel = async () => {
  const url = await getWebSocketUrl()
  if ("WebSocket" in window) {
    socket.value = new WebSocket(url as any);
  } else if ("MozWebSocket" in window) {
    // @ts-ignore
    socket.value = new MozWebSocket(url as any);
  } else {
    alert("暂时不支持websocket")
    return null;
  }

  socket.value.onopen = (e: any) => {
    let params = {
      "header": {
        "app_id": apiData.value.APPID,
        "uid": "aef9f963-7"
      },
      "parameter": {
        "chat": {
          "domain": "general",
          "temperature": 0.5,
          "max_tokens": 1024
        }
      },
      "payload": {
        "message": {
          "text": [
            {
              "role": "user",
              "content": messageContent.value
            }
          ]
        }
      }
    };

    socket.value.send(JSON.stringify(params));
    clearMessageContent();
  }

  socket.value.onmessage = (e: any) => {
    let obj = JSON.parse(e.data);
    let dataArray = obj.payload.choices.text;
    

    for (let i = 0; i < dataArray.length; i++) {
      appendLastMessageContent(dataArray[i].content)
    }

    let temp = JSON.parse(e.data);
    if (temp.header.code !== 0) {
      isTalking.value = false;
      socket.value.close();
    }
    if (temp.header.code === 0) {
      if (e.data && temp.header.status === 2) {
        setTimeout(() => {
          isTalking.value = false;
          socket.value.close();
        }, 1000);
      }
    }
  };
};

const appendLastMessageContent = (content: string) => {
  messageList.value[messageList.value.length - 1].content += content
};

const clearMessageContent = () => {
  messageContent.value = ""
};

const scrollToBottom = () => {
  if (!chatListDom.value) return;
  scrollTo(0, chatListDom.value.scrollHeight);
};

watch(messageList.value, () => nextTick(() => scrollToBottom()));
</script>

<style scoped>
pre {
  font-family: -apple-system, "Noto Sans", "Helvetica Neue", Helvetica,
    "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB",
    "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN",
    "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti",
    SimHei, "WenQuanYi Zen Hei Sharp", sans-serif;
}
.prose :where(ol > li):not(:where([class~="not-prose"],[class~="not-prose"] *))::marker {
  color: white;
}
</style>
